package com.ku.kill.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.util.concurrent.RateLimiter;
import com.ku.kill.controller.vo.UserVO;
import com.ku.kill.error.BusinessException;
import com.ku.kill.error.EmBusinessError;
import com.ku.kill.mq.MQProvider;
import com.ku.kill.response.CommonReturnType;
import com.ku.kill.service.ItemService;
import com.ku.kill.service.OrderService;
import com.ku.kill.service.PromoService;
import com.ku.kill.service.model.OrderModel;
import com.ku.kill.util.CodeUtil;
import com.sun.org.apache.xpath.internal.operations.Bool;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @program: kill2
 * @description:
 * @author: JokerKu
 * @create: 2020-04-03 13:31
 **/
@RequestMapping("/order")
@RestController
@CrossOrigin(origins = {"*"}, allowCredentials = "true")
@Slf4j
public class OrderController {

    @Autowired
    private OrderService orderService;
    @Autowired
    private HttpServletRequest httpServletRequest;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ItemService itemService;

    @Autowired
    private MQProvider mqProvider;
    @Autowired
    private PromoService promoService;

    //线程池来泄洪
    private ExecutorService executorService;

    //令牌桶算法限流
    private RateLimiter orderCreateRateLimiter;

    //流量削峰 ——>  队列泄洪
    @PostConstruct //在OrderController初始化后执行的操作
    public void init() {
        //设置核心池大小
        int corePoolSize = 20;
        //设置线程池最大能接受多少线程
        int maximumPoolSize = 30;
        //当前线程数大于corePoolSize、小于maximumPoolSize时，超出corePoolSize的线程数的生命周期
        long keepActiveTime = 5;
        //设置时间单位，秒
        TimeUnit timeUnit = TimeUnit.SECONDS;
        //设置线程池缓存队列的排队策略为FIFO，并且指定缓存队列大小为20
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(20);
        //创建ThreadPoolExecutor线程池对象，并初始化该对象的各种参数
        executorService = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepActiveTime, timeUnit, workQueue);


        //令牌桶限流 算法
        orderCreateRateLimiter = RateLimiter.create(300);//初始300个令牌
    }


    @GetMapping("/generateVerifyCode")
    public void generateVerifyCode(@RequestParam("token") String token, HttpServletResponse response) throws BusinessException, IOException {
        //判断用户是否登录
        if (StrUtil.isBlank(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"不能生成验证码");
        }
        String uerVoStr = redisTemplate.opsForValue().get(token);
        UserVO userVO = JSONUtil.toBean(uerVoStr, UserVO.class);
        if (userVO == null || userVO.getId() == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"不能生成验证码");
        }
        //登录了，生成验证码
        Map<String, Object> codeMap = CodeUtil.generateCodeAndPic();
        //将验证码的值存入redis中
        redisTemplate.opsForValue().set("verify_code_"+userVO.getId(), codeMap.get("code").toString());
        System.out.println("验证码的值为："+codeMap.get("code"));

        //验证码图片返回给前端
        ImageIO.write((RenderedImage) codeMap.get("codePic"), "jpeg", response.getOutputStream());
    }

    /**
     * 生成秒杀令牌
     * @param itemId
     * @param amount
     * @param promoId
     * @param token
     * @param verifyCode
     * @return
     * @throws BusinessException
     */
    @PostMapping("/generateKillToken")
    public CommonReturnType generateKillToken(@RequestParam("itemId") Integer itemId,
                                              @RequestParam("amount") Integer amount,
                                              @RequestParam(name = "promoId", required = false) Integer promoId,
                                              @RequestParam("token") String token,
                                              @RequestParam("verifyCode") String verifyCode) throws BusinessException {

        //判断用户是否登录
        if (StrUtil.isBlank(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN);
        }
        String uerVoStr = redisTemplate.opsForValue().get(token);
        UserVO userVO = JSONUtil.toBean(uerVoStr, UserVO.class);
        if (userVO == null || userVO.getId() == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN);
        }
        //判断验证码是否相等
        if (StrUtil.isEmpty(verifyCode)){
            throw new BusinessException(EmBusinessError.PARMATER_VALIDATION_ERROR,"非法请求");
        }
        //取出redis中的验证码
        String inRedisCode = redisTemplate.opsForValue().get("verify_code_" + userVO.getId());

        if (!StrUtil.endWithIgnoreCase(verifyCode,inRedisCode)){
            throw new BusinessException(EmBusinessError.VERIFY_CODE_ERROR);
        }

        String promoToken = promoService.generateSecondKillToken(promoId, itemId, userVO.getId());
        if (StrUtil.isEmpty(promoToken)) {
            throw new BusinessException(EmBusinessError.KILL_TOKEN_NOT_EXIT);
        }
        return CommonReturnType.create(promoToken, "success");
    }


    @PostMapping("/createorder")
    public CommonReturnType createOrder(@RequestParam("itemId") Integer itemId,
                                        @RequestParam("amount") Integer amount,
                                        @RequestParam(name = "promoId", required = false) Integer promoId,
                                        @RequestParam("token") String token,
                                        @RequestParam(value = "promoToken", required = false) String promoToken) throws BusinessException {
        //判断是否获得令牌
        if (!orderCreateRateLimiter.tryAcquire()){
            throw new BusinessException(EmBusinessError.RATE_LIMITE);
        }

        if (itemId == null || amount == null) {
            throw new BusinessException(EmBusinessError.PARMATER_VALIDATION_ERROR);
        }

        //-----------------使用token+redis来判断用户的登录状态------
        if (StrUtil.isBlank(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN);
        }
        String uerVoStr = redisTemplate.opsForValue().get(token);
        UserVO userVO = JSONUtil.toBean(uerVoStr, UserVO.class);
        if (userVO == null || userVO.getId() == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN);
        }

        //判断秒杀令牌是否正确
        if (promoId != null) {
            String inRedisPromoToken = redisTemplate.opsForValue().get("promo_token_" + promoId + "_itemId_" + itemId + "_userId_" + userVO.getId());
            if (inRedisPromoToken == null || promoToken == null) {
                throw new BusinessException(EmBusinessError.PARMATER_VALIDATION_ERROR, "秒杀令牌校验失败");
            }
            if (!StrUtil.equals(promoToken, inRedisPromoToken)) {
                throw new BusinessException(EmBusinessError.PARMATER_VALIDATION_ERROR, "秒杀令牌校验失败");
            }
        }

        //可以 下单
//        OrderModel orderModel = orderService.createOrder(userVO.getId(), promoId, itemId, amount);

        //todo 在生成流水和订单前 先判断库存是否售罄   ---> 被移到了生成令牌的方法里

        //todo 同步调用线程池的submit方法
        //拥塞窗口为20的等待队列，用队列来泄洪
        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                //todo 1.加入库存流水状态 （初始化状态）
                String stockLogId = itemService.initStockLog(itemId, amount);

                //todo 2.再使用mq,将数据传入MQ中使用事务性消息
                boolean sendResult = mqProvider.transactionAsyncReduceStock(stockLogId, userVO.getId(), promoId, itemId, amount);
                //判断下单结果
                if (!sendResult) {
                    throw new BusinessException(EmBusinessError.UNKONMW_ERROR, "下单失败");
                }
                return null;
            }
        });


        try {
            future.get();
        } catch (Exception e) {
           throw new BusinessException(EmBusinessError.UNKONMW_ERROR);
        }

        return CommonReturnType.create(null);
    }
}
